package com.epfresh.api.ucrop.task;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;

import com.epfresh.api.ucrop.util.BitmapLoadUtils;

import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.OutputStream;


/**
 * Creates and returns a Bitmap for a given Uri(String url).
 * inSampleSize is calculated based on requiredWidth property. However can be adjusted if OOM occurs.
 * If any EXIF config is found - bitmap is transformed properly.
 */
public class BitmapProcessTask extends AsyncTask<Void, Void, Exception> {

    private static final String TAG = "BitmapWorkerTask";

    private final Context mContext;
    private Uri mInputUri;
    private final Uri mOutputUri;
    private final int mRequiredWidth;
    private final int mRequiredHeight;

    private final BitmapProcessCallback mBitmapLoadCallback;

    private boolean isOutputFile = true;


    public BitmapProcessTask(@NonNull Context context,
                             @Nullable Uri inputUri, @Nullable Uri outputUri,
                             int requiredWidth, int requiredHeight,
                             BitmapProcessCallback loadCallback) {
        mContext = context;
        mInputUri = inputUri;
        mOutputUri = outputUri;
        mRequiredWidth = requiredWidth;
        mRequiredHeight = requiredHeight;
        mBitmapLoadCallback = loadCallback;
    }

    public boolean isOutputFile() {
        return isOutputFile;
    }

    public void setOutputFile(boolean outputFile) {
        isOutputFile = outputFile;
    }

    @Override
    @NonNull
    protected Exception doInBackground(Void... params) {
        if (mInputUri == null || mOutputUri == null) {
            return new NullPointerException("Uri cannot be null");
        }


        final ParcelFileDescriptor parcelFileDescriptor;
        try {
            parcelFileDescriptor = mContext.getContentResolver().openFileDescriptor(mInputUri, "r");
        } catch (FileNotFoundException e) {
            return e;
        }

        final FileDescriptor fileDescriptor;
        if (parcelFileDescriptor != null) {
            fileDescriptor = parcelFileDescriptor.getFileDescriptor();
        } else {
            return new NullPointerException("ParcelFileDescriptor was null for given Uri");
        }

        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
        if (options.outWidth == -1 || options.outHeight == -1) {
            return new IllegalArgumentException("Bounds for bitmap could not be retrieved from Uri");
        }

        options.inSampleSize = BitmapLoadUtils.calculateInSampleSize(options, mRequiredWidth, mRequiredHeight);
        options.inJustDecodeBounds = false;

        Bitmap decodeSampledBitmap = null;

        boolean decodeAttemptSuccess = false;
        while (!decodeAttemptSuccess) {
            try {
                decodeSampledBitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
                decodeAttemptSuccess = true;
            } catch (OutOfMemoryError error) {
                Log.e(TAG, "doInBackground: BitmapFactory.decodeFileDescriptor: ", error);
                options.inSampleSize++;
            }
        }

        if (decodeSampledBitmap == null) {
            return new IllegalArgumentException("Bitmap could not be decoded from Uri");
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            BitmapLoadUtils.close(parcelFileDescriptor);
        }

        int exifOrientation = BitmapLoadUtils.getExifOrientation(mContext, mInputUri);
        int exifDegrees = BitmapLoadUtils.exifToDegrees(exifOrientation);
        int exifTranslation = BitmapLoadUtils.exifToTranslation(exifOrientation);

        Matrix matrix = new Matrix();
        if (exifDegrees != 0) {
            matrix.preRotate(exifDegrees);
        }
        if (exifTranslation != 1) {
            matrix.postScale(exifTranslation, 1);
        }
        if (!matrix.isIdentity()) {
            decodeSampledBitmap = BitmapLoadUtils.transformBitmap(decodeSampledBitmap, matrix);
        }

        if (isOutputFile) {
            Exception e = loadBitmapFile(decodeSampledBitmap);
            if (e != null) {
                return e;
            }
            decodeSampledBitmap = null;
        }

        return null;
    }

    protected Exception loadBitmapFile(Bitmap decodeSampledBitmap) {
        OutputStream outputStream = null;
        try {
            outputStream = mContext.getContentResolver().openOutputStream(mOutputUri);
            decodeSampledBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
            if (!decodeSampledBitmap.isRecycled())
                decodeSampledBitmap.recycle();
            decodeSampledBitmap = null;
        } catch (Exception e) {
            return e;
        } finally {
            BitmapLoadUtils.close(outputStream);
        }
        return null;
    }


    @Override
    protected void onPostExecute(Exception result) {
        if (result == null) {
            mBitmapLoadCallback.onBitmapLoaded(mInputUri, mOutputUri);
        } else {
            mBitmapLoadCallback.onFailure(result);
        }
    }

    public interface BitmapProcessCallback {

        void onBitmapLoaded(Uri in, Uri out);

        void onFailure(Exception bitmapWorkerException);

    }

}